from sys import exit
from Tkinter import *
import random


class Player(object):
    def __init__(self,board):
        self.board = board
        self.defaultField = None
        self.possessions = {}
        self.values = {}
        self.name = "default"
    
    #Sets the name of the player, usually a numerically distinguished
    def setName(self,name): self.name = name
    
    #The string representation of a player is the player's name
    def __str__(self): return self.name
    
    #add a single value to the values dictionary
    def addValue(self,valName): self.values[valName] = None
    #set a specific value
    def setValue(self,valName,value): self.values[valName] = value
    #Get a specific value
    def getValue(self,valName): return self.values[valName]
    
    #Add an empty list of items to the dictionary
    #The first field is the default field
    def addField(self,fieldName,*args):
        self.possessions[fieldName] = []
        if len(args) == 1: self.possessions[fieldName] = args[0]
        elif len(args) > 0:
            print("Invalid arguments to addField()")
            exit()
        if self.defaultField == None: self.defaultField = fieldName
        
    #Get the contents of a given field (default or otherwise)
    #This is a direct reference, not a duplicate list
    def getContents(self,*args):
        #Get the field
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method getContents()")
            exit()
        #Return the list in question
        return self.possessions[field]

    #Add items to a field
    #Takes either (self,field,additions) or (self,additions)
    def addItems(self,additions,*args):
        field = self.defaultField
        #Read in the arguments
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print ("Invalid arguments to addItems()")
            exit()
        #Add the additions to the given field
        self.possessions[field] += additions
        
    #Add a single item to a field
    def add(self,addition,*args):
        field = self.defaultField
        #Read in the arguments
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print ("Invalid arguments to addItems()")
            exit()
        #Add the addition to the given field
        self.possessions[field].append(addition)
    
    #Remove a list of items from player possession
    def removeItems(self,items,*args):
        #Get the field in question
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method remove()")
            exit()
        #Iterate over the items to remove, and remove them
        for i in items:
            self.remove(i,field)

    #Remove an item from player possession
    def remove(self,item,*args):
        #Get the field in question
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method remove()")
            exit()
        #If the item is in the field, remove it
        if item in self.possessions[field]: self.possessions[field].remove(item)
        #Otherwise, it is an error
        else:
            print("Invalid removal of item " + str(item) + " from field " + str(field) + " in Player method remove()")
            exit()
    
    def sortItems(self,func,*args):
        #Get the field in question
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method sortItems()")
            exit()
        self.possessions[field] = func(self.possessions[field])

    #Play an item at a given position
    def play(self,item,position,*args):
        field = self.defaultField
        #Read in the arguments
        if len(args) == 1: field = args[0]
        elif len(args) >0:
            print("Invalid arguments to play()")
            exit()
        #If the item can be found in player possession
        if item in self.possessions[field]:
            #Remove it from player possession
            self.possessions[field].remove(item)
            #And place it on the board
            self.board.place(item,position)
        #Otherwise, print an error
        else:
            print("Invalid Move Given: Item " + str(item) + " Not In " + str(field) +" Field.")
            exit()
            
    #Take a item from the board and put it in the player's possession
    #Takes either (self,field,item,position) or (self,item,position)
    def take(self,item,position,*args):
        field = self.defaultField
        #Read in the arguments
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method take()")
            exit()
        #Perform the operation
        self.board.remove(item,position)
        self.possessions[field].append(item)

    #Take all items from the board at a given position and put them in the player's possession
    #Takes either (self,field,position) or (self,position)
    def takeAll(self,position,*args):
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method takeAll()")
            exit()
        self.possessions[field] += self.board.contents(position)
        self.board.empty(position)
    
    def has(self,item,*args):
        field = self.defaultField
        if len(args) == 1: field = args[0]
        elif len(args) > 0:
            print("Invalid arguments to Player method has()")
            exit()
        return item in self.possessions[field] 

    #Move a item already on the board from one position to another by removing it and placing it again
    def move(self,item,frompos,topos):
        self.board.remove(item,frompos)
        self.board.place(item,topos)
    
    #Asks the player to select something (by string) from a list
    def select(self,prompt,lst):
        #Start the item at None
        selection = None
        qFlag = False #Exit flag
        #while the item given is not in the field
        while selection == None and not qFlag:
            prompterText = []
            #Iterate over the list to select from, making the items into text for the prompter
            for x in lst: prompterText.append(str(x))
            temp = Prompter(prompt,prompterText).getVal()
            
            #Iterate over the list looking for a match to the given input
            for x in lst:
                if str(x) == temp: selection = x
            
            #The exit command, which leaves the loop and returns None
            qFlag = temp == "\exit"
        return selection

#Create a list of players
def initPlayers(num,board,fields,values):
    players = []
    for x in range(num):
        #Create a temporary player
        temp = Player(board)
        #Add the appropriate fields
        for f in fields: temp.addField(f)
        #Add the appropriate values
        for v in values: temp.addValue(v)
        #Append the temporary player to the list
        players.append(temp)
    return players



class Board(object):
    #The board is a mapping from positions to lists of contents
    def __init__(self,*positionList):
        self.locations = {}
        #Configure the board with empty positions
        #Add each position to the dictionary, mapping to an empty list
        for p in positionList:
            self.locations[p] = []
    
    #Give the board a configuration
    def configure(self,config):
        self.locations = config
    
    #Get contents from the board, possibly at a given position
    def contents(self,*args):
        #Get the contents of a given position, a list of items
        if len(args) == 1: return self.locations[args[0]]
        #Get the full contents of the board by concatenating the contents from every location on the board
        elif len(args) == 0:
            list = []
            #Iterate over the values in the dictionary and concatenate them
            for itemList in self.locations.values(): list += itemList
            return list
        else:
            prin("Invalid arguments to Board method contents()")
            exit()

    #Play a item at the given position
    def place(self,item,position): self.locations[position].append(item)
    
    #Remove a item from a given position on the board
    def remove(self,item,position):
        #If the item is at the position, remove it
        if item in self.locations[position]: self.locations[position].remove(item)
        #Otherwise, print an error message and exit
        else:
            print("Invalid removal of " + str(item) + " from position " + str(position) + " in Board method remove().")
            exit()

    #Remove all items from the given position, without saving the data
    def empty(self,position): self.locations[position] = []




class Card(object): #Card object
    def __init__(self, st, val, rnk):
        self.suit = st
        self.value = val
        self.rank = rnk
    #The standard string representation of a card
    #It is recommended that child classes with overwriting versions of this method are made to support various games
    def __str__(self): return(self.rank + " of " + self.suit)

#Definition of a sort by rank
def sortByVal(cards): cards.sort(key=lambda x: x.value)

#Full sorting used to divide into suits, sort the suits, then combine them
def sortPokerCards(cards):
    diamonds = filter(lambda x: x.suit == "Diamonds",cards)
    clubs = filter(lambda x: x.suit == "Clubs",cards)
    hearts = filter(lambda x: x.suit == "Hearts",cards)
    spades = filter(lambda x: x.suit == "Spades",cards)
    suits = diamonds,clubs,hearts,spades
    sorted = []
    for s in suits:
        if s != None:
            sortByVal(s)
            sorted += s
    return sorted

#Get a reference to a card, so that it can be tracked, if necessary
def getCard(cards,name):
    for x in cards:
        if str(x) == name: return x

def suitmatch(lst, suit):
    for card in lst:
        if card.suit == suit: return True
    return False

def rankmatch(lst,rank):
    for card in lst:
        if card.rank == rank: return True
    return False


class Deck(object):
    #Initialize an empty deck
    def __init__(self): self.cards = []
    
    #Add a list of existing cards to the deck
    def addCards(self,cards): self.cards += cards
    #Add a single card
    def addCard(self,card): self.cards.append(card)
    
    #Add a suit dictionary to the deck
    #A suit dictionary maps a suit to a list of card values in that suit
    #Each list of cards must be input in ascending order, so that they are assigned the right comparison values
    #The values are assumed to disregard suit, so a 2 of Spades and a 2 of Hearts, for example, are equal
    def addSuitDict(self,suitdict):
        #Iterate over the suits
        for suit in suitdict.keys():
            val = 0
            #Initialize each card in that suit with ascending values, starting at 0
            #Append the cards to the card dictionary
            for kard in suitdict[suit]:
                self.cards.append(Card(suit, val, kard))
                val = val+1
                
    #Shuffle the deck
    def shuffle(self): random.shuffle(self.cards)

    #Draw number cards from the deck
    def draw(self,*args):
        if len(args) == 1:
            out = []
            for x in range(args[0]): out.append(self.draw())
            return out
        elif len(args) > 0:
            print("Invalid arguments to Deck method draw()")
            exit()
        else:
            return self.cards.pop()

#A prompter object
class Prompter(object):
    def __init__(self,prompt,choices):
        self.selection = None
        #Create the root
        root = Tk()
        root.title("Prompter")

        #Create the frame for the prompter
        frame = Frame(root)
        frame.pack()
        
        #num is used to build columns and rows correctly
        num = -1
        colSize = 10
        #Put each choice into the window
        #Whichever one gets chosen will become the selection value of this prompter
        for c in choices:
            num += 1
            
            #This ugly nesting nonsense is necessary to fix problems that appear with the Button initialization
            def f(c):
                #The outer function essentially "pulls" c from the for loop
                #This inner function is actually the one we want, the one that puts c and closes the window
                def g():
                    self.selection = c
                    frame.quit()
                return g
            
            #Create the button, and place it in the grid appropriately
            Button(frame,text=c,command=f(c)).grid(row=num%colSize + 1,column=num/colSize,sticky=W+E+N+S)
            
        #Define the quit function
        def qFunc():
            self.selection = '\exit'
            frame.quit()
        #Create the quit button, and place it at the far right, at the top
        Button(frame,text="Exit",fg="red",command=qFunc).grid(row=0,column=num/colSize + 1,sticky=W+E+N+S)
        
        #Label the window with the prompt
        Label(frame,text=prompt,font=("Helvetica",16)).grid(row=0,column=num/colSize / 2)
        
        root.mainloop()
        root.destroy()

    def getVal(self): return self.selection


def PokerDeck():
    d = Deck()
    d.addSuitDict({"Spades":["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"], "Hearts":["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"], "Diamonds":["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"],"Clubs":["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]})
    return d

"""
strs = PokerDeck.cards
strs = map(lambda x: str(x),strs)
test = Prompter("Pick a card.",strs)
print test.getVal()
"""